/*
 * @(#)TermWeightList.java	1.0 01/01/05
 *
 * Copyright 2007 Fabio Gasparetti. All rights reserved.
 */
package org.tabularium.text;

import java.io.*;

import java.util.*;

import org.tabularium.text.analysis.*;

/**
 * Provides access to a term-weight list where duplications are allowed.
 * 
 * Useful to represent documents where each word has its own weight
 * and the positions are relevant. Terms can include punctuation useful
 * for POST post-analysis. Weights of punctuation should not be relevant.
 * 
 * @author Fabio Gasparetti
 * @version 1.0, 01/01/05
 */
public class TermWeights implements Serializable, Comparable {
	public static final double DEFAULT_WEIGHT = 1d;

	protected String terms[] = null;

	protected double weights[] = null;

	public TermWeights() {
		terms = new String[0];
		weights = new double[0];
	}

	/**
	 * Returns an array of the terms in the list.
	 */
	public String[] terms() {
		return terms;
	}

	/**
	 * Returns an array of the term's weights.
	 */
	public double[] weights() {
		return weights;
	}

	public int size() {
		return terms.length;
	}

	/**
	 * Returns a new TermFreqVector instance given an array of tokens.
	 */
	static public TermWeights build(String[] tokens, double[] weights) {
		TermWeights vect = new TermWeights();
		if (tokens != null) {
			vect.terms = tokens;
			vect.weights = weights;
		}
		return vect;
	}

	/**
	 * Returns a new TermFreqVector instance given an array of tokens.
	 */
	static public TermWeights build(String[] tokens) {
		return build(tokens, TermWeights.DEFAULT_WEIGHT);
	}

	/**
	 * Returns a new TermFreqVector instance given an array of tokens.
	 */
	static public TermWeights build(String[] tokens, double weight) {
		TermWeights vect = new TermWeights();
		if (tokens != null) {
			vect.terms = tokens;
			vect.weights = new double[tokens.length];
			for (int i = 0; i < vect.weights.length; i++)
				vect.weights[i] = weight;
		}
		return vect;
	}

	/**
	 * Returns a new TermFreqVector instance given a string.
	 */
	static public TermWeights build(String s, Analyzer analyzer)
			throws Exception {
		return TermWeights.build(s, analyzer, TermWeights.DEFAULT_WEIGHT);
	}

	/**
	 * Returns a new TermFreqVector instance given a string.
	 */
	static public TermWeights build(String s, Analyzer analyzer, double weight)
			throws Exception {
		TermWeights vect = new TermWeights();
		if (s != null) {
			vect.terms = analyzer.tokenArray(new StringReader(s));
			vect.weights = new double[vect.terms.length];
			for (int i = 0; i < vect.weights.length; i++)
				vect.weights[i] = weight;
		}
		return vect;
	}

	/**
	 * Merges the given TermWeights object with the called object.
	 * 
	 * The resulting array is ordered alphabetically. If a term compares in both
	 * arrays, the highest weight is kept. Returns the updated TermWeights
	 * object.
	 */
	public TermWeights merge(TermWeights w) {
		TreeMap m = new TreeMap();
		for (int i = 0; i < terms.length; i++) {
			m.put(terms[i], new Double(weights[i]));
		}
		for (int i = 0; i < w.terms.length; i++) {
			Double weight = (Double) m.get(w.terms[i]);
			if ((weight == null)
					|| ((weight != null) && (w.weights[i] > weight
							.doubleValue()))) {
				m.put(w.terms[i], new Double(w.weights[i]));
			}
		}
		terms = new String[m.size()];
		weights = new double[m.size()];
		Iterator keyIter = m.keySet().iterator();
		Iterator valueIter = m.values().iterator();
		for (int i = 0; keyIter.hasNext(); i++) {
			terms[i] = (String) keyIter.next();
			weights[i] = ((Double) valueIter.next()).doubleValue();
		}
		return this;
	}

	public void timesScalar(double d) {
		for (int i = 0; i < weights.length; i++) {
			weights[i] *= d;
		}
	}
	
	/**
	 * Merges the given TermWeights object with the called object.
	 * 
	 * The original order of the two sequences is kept (no sort is performed).
	 */
	public void append(TermWeights w) {
		String[] newTerms = new String[w.terms.length + terms.length];
		double[] newWeights = new double[w.weights.length + weights.length];
		System.arraycopy(terms, 0, newTerms, 0, terms.length);
		System.arraycopy(weights, 0, newWeights, 0, weights.length);
		System.arraycopy(w.terms, 0, newTerms, terms.length, w.terms.length);
		System.arraycopy(w.weights, 0, newWeights, weights.length, w.weights.length);
		terms = newTerms;
		weights = newWeights;
	}

	/**
	 * If terms are duplicated, associated weights will be summed up.
	 * @return
	 */
	public TermWeightVector toTermWeightVector() {
		return TermWeightVector.build(terms, weights);		
	}
	
	/**
	 * Returns a space-separated sequence of terms.
	 */
	public String asString() {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < terms.length; i++) {
			buf.append(terms[i] + " ");
		}
		return buf.toString().trim();
	}
	
	/**
	 * Returns a String format: "[term1:weight1 t2:w2 t3:w3...]"
	 */
	public String toString() {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < terms.length; i++) {
			buf.append(terms[i] + ":" + weights[i] + " ");
		}
		return "[" + buf.toString().trim() + "]";
	}
	
	/**
	 * Removes any occurences of the given term. 
	 */
	public TermWeights filter(String term) {
		// indexes of term in the array
		int[] idxs = new int[terms.length];		
		int j = 0;
		for (int i = 0; i < terms.length; i++) {
			if (terms[i].compareTo(term) == 0) {
				idxs[j++] = i;
			}			
		}		
		if (j == 0)
			return this;
		String[] newTerms = new String[terms.length - j];
		double[] newWeights = new double[terms.length - j];
		// optimize with arraycopy?
		for (int i = 0, k = 0, i1 = 0; i < terms.length; i++) {
			if (i != idxs[k]) {
				newTerms[i1] = terms[i];
				newWeights[i1] = weights[i];
				i1++;
			} else {
				k++;
			}
		}
		terms = newTerms;
		weights = newWeights;
		return this;
	}

	private void readObject(java.io.ObjectInputStream stream)
			throws IOException, ClassNotFoundException {
		terms = (String[]) stream.readObject();
		weights = (double[]) stream.readObject();
	}

	private void writeObject(java.io.ObjectOutputStream stream)
			throws IOException {
		stream.writeObject(terms);
		stream.writeObject(weights);
	}
	
	

	// debug
	public static void main(String[] args) throws Exception {
		StandardAnalyzer analyzer = new StandardAnalyzer();
		TokenStream a1 = analyzer.tokenStream(new StringReader(
				"ciao questa e una prova questa prova"));
		ArrayList tokens = new ArrayList();
		while (true) {
			String w = a1.next();
			if (w != null) {
				tokens.add(w);
			} else
				break;
		}
		TermWeights vect1 = TermWeights.build((String[]) tokens
				.toArray(new String[] {}));
		System.out.println(vect1.toString());

		a1 = analyzer.tokenStream(new StringReader(
				"ciao questa e una seconda prova questa"));
		tokens = new ArrayList();
		while (true) {
			String w = a1.next();
			if (w != null) {
				tokens.add(w);
			} else
				break;
		}
		TermWeights vect2 = TermWeights.build((String[]) tokens
				.toArray(new String[] {}));

		System.out.println(vect2.toString());
		vect1.append(vect2);
		System.out.println(vect1.toString());
		vect1 = new TermWeights();
		vect1.filter("questa");
		System.out.println(vect1.toString());

	}

	/**
	 * Compares the two lists of terms in their original order from the two TermWeights objects.
	 * If a pair of terms differs, the results of compareTo comparison is returned.
	 * If a pair of terms corresponds but the corresponding weights differs, {-1,0,1} are returned according to the the sign of the difference.
	 * If one list ends before the other, the length difference is returned. 
	 */
	public int compareTo(Object o) {
		if (this == o)
			return 0;
		TermWeights v = (TermWeights)o;
		int len1 = this.terms.length;
		int len2 = v.terms.length;
		int diff = 0;
        for (int i = 0; i < len1 && i < len2; i++) {
                diff = v.terms[i].compareTo(terms[i]);
                if (diff == 0)
                	diff = (int)Math.signum(v.weights[i] - weights[i]);
                if(diff != 0)
                	break;
        }
        if (diff == 0)
        	diff = len2 - len1;
        return diff;
	}}
